// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable InconsistentNaming

/* DataScale.cs --
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Drawing;
using System.Runtime.Serialization;

#endregion

#nullable enable

namespace AM.Drawing.Charting;

/// <summary>
/// The DateScale class inherits from the <see cref="Scale" /> class, and implements
/// the features specific to <see cref="AxisType.Date" />.
/// </summary>
/// <remarks>
/// DateScale is a cartesian axis with calendar dates or times.  The actual data values should
/// be created with the <see cref="XDate" /> type, which is directly translatable to a
/// <see cref="System.Double" /> type for storage in the point value arrays.
/// </remarks>
[Serializable]
internal class DateScale
    : Scale
{
    #region constructors

    /// <summary>
    /// Default constructor that defines the owner <see cref="Axis" />
    /// (containing object) for this new object.
    /// </summary>
    /// <param name="owner">The owner, or containing object, of this instance</param>
    public DateScale
        (
            Axis owner
        )
        : base (owner)
    {
        // пустое тело конструктора
    }

    /// <summary>
    /// The Copy Constructor
    /// </summary>
    /// <param name="rhs">The <see cref="DateScale" /> object from which to copy</param>
    /// <param name="owner">The <see cref="Axis" /> object that will own the
    /// new instance of <see cref="DateScale" /></param>
    public DateScale
        (
            Scale rhs,
            Axis owner
        )
        : base (rhs, owner)
    {
        // пустое тело конструктора
    }

    /// <inheritdoc cref="Scale.Clone"/>
    public override Scale Clone
        (
            Axis owner
        )
    {
        return new DateScale (this, owner);
    }

    #endregion

    #region properties

    /// <inheritdoc cref="Scale.Type"/>
    public override AxisType Type => AxisType.Date;

    /// <inheritdoc cref="Scale.Min"/>
    public override double Min
    {
        get => _min;
        set
        {
            _min = XDate.MakeValidDate (value);
            _minAuto = false;
        }
    }

    /// <inheritdoc cref="Scale.Max"/>
    public override double Max
    {
        get => _max;
        set
        {
            _max = XDate.MakeValidDate (value);
            _maxAuto = false;
        }
    }

    #endregion

    #region methods

    /// <inheritdoc cref="Scale.CalcMajorTicValue"/>
    internal override double CalcMajorTicValue
        (
            double baseVal,
            double tic
        )
    {
        var xDate = new XDate (baseVal);

        switch (_majorUnit)
        {
            case DateUnit.Year:
            default:
                xDate.AddYears (tic * _majorStep);
                break;

            case DateUnit.Month:
                xDate.AddMonths (tic * _majorStep);
                break;

            case DateUnit.Day:
                xDate.AddDays (tic * _majorStep);
                break;

            case DateUnit.Hour:
                xDate.AddHours (tic * _majorStep);
                break;

            case DateUnit.Minute:
                xDate.AddMinutes (tic * _majorStep);
                break;

            case DateUnit.Second:
                xDate.AddSeconds (tic * _majorStep);
                break;

            case DateUnit.Millisecond:
                xDate.AddMilliseconds (tic * _majorStep);
                break;
        }

        return xDate.XLDate;
    }

    /// <inheritdoc cref="Scale.CalcMinorTicValue"/>
    internal override double CalcMinorTicValue
        (
            double baseVal,
            int iTic
        )
    {
        var xDate = new XDate (baseVal);

        switch (_minorUnit)
        {
            case DateUnit.Year:
            default:
                xDate.AddYears (iTic * _minorStep);
                break;

            case DateUnit.Month:
                xDate.AddMonths (iTic * _minorStep);
                break;

            case DateUnit.Day:
                xDate.AddDays (iTic * _minorStep);
                break;

            case DateUnit.Hour:
                xDate.AddHours (iTic * _minorStep);
                break;

            case DateUnit.Minute:
                xDate.AddMinutes (iTic * _minorStep);
                break;

            case DateUnit.Second:
                xDate.AddSeconds (iTic * _minorStep);
                break;
        }

        return xDate.XLDate;
    }

    /// <inheritdoc cref="Scale.CalcMinorStart"/>
    internal override int CalcMinorStart
        (
            double baseVal
        )
    {
        switch (_minorUnit)
        {
            case DateUnit.Year:
            default:
                return (int)((_min - baseVal) / (365.0 * _minorStep));

            case DateUnit.Month:
                return (int)((_min - baseVal) / (28.0 * _minorStep));

            case DateUnit.Day:
                return (int)((_min - baseVal) / _minorStep);

            case DateUnit.Hour:
                return (int)((_min - baseVal) * XDate.HoursPerDay / _minorStep);

            case DateUnit.Minute:
                return (int)((_min - baseVal) * XDate.MinutesPerDay / _minorStep);

            case DateUnit.Second:
                return (int)((_min - baseVal) * XDate.SecondsPerDay / _minorStep);
        }
    }

    /// <inheritdoc cref="Scale.CalcBaseTic"/>
    internal override double CalcBaseTic()
    {
        if (_baseTic != PointPairBase.Missing)
        {
            return _baseTic;
        }
        else
        {
            int year, month, day, hour, minute, second, millisecond;
            XDate.XLDateToCalendarDate (_min, out year, out month, out day, out hour, out minute,
                out second, out millisecond);
            switch (_majorUnit)
            {
                case DateUnit.Year:
                default:
                    month = 1;
                    day = 1;
                    hour = 0;
                    minute = 0;
                    second = 0;
                    millisecond = 0;
                    break;

                case DateUnit.Month:
                    day = 1;
                    hour = 0;
                    minute = 0;
                    second = 0;
                    millisecond = 0;
                    break;

                case DateUnit.Day:
                    hour = 0;
                    minute = 0;
                    second = 0;
                    millisecond = 0;
                    break;

                case DateUnit.Hour:
                    minute = 0;
                    second = 0;
                    millisecond = 0;
                    break;

                case DateUnit.Minute:
                    second = 0;
                    millisecond = 0;
                    break;

                case DateUnit.Second:
                    millisecond = 0;
                    break;

                case DateUnit.Millisecond:
                    break;
            }

            var xlDate = XDate.CalendarDateToXLDate (year, month, day, hour, minute, second, millisecond);
            if (xlDate < _min)
            {
                switch (_majorUnit)
                {
                    case DateUnit.Year:
                    default:
                        year++;
                        break;

                    case DateUnit.Month:
                        month++;
                        break;

                    case DateUnit.Day:
                        day++;
                        break;

                    case DateUnit.Hour:
                        hour++;
                        break;

                    case DateUnit.Minute:
                        minute++;
                        break;

                    case DateUnit.Second:
                        second++;
                        break;

                    case DateUnit.Millisecond:
                        millisecond++;
                        break;
                }

                xlDate = XDate.CalendarDateToXLDate (year, month, day, hour, minute, second, millisecond);
            }

            return xlDate;
        }
    }

    /// <inheritdoc cref="Scale.CalcNumTics"/>
    internal override int CalcNumTics()
    {
        var nTics = 1;

        int year1, year2, month1, month2, day1, day2, hour1, hour2, minute1, minute2;
        int second1, second2, millisecond1, millisecond2;

        XDate.XLDateToCalendarDate (_min, out year1, out month1, out day1,
            out hour1, out minute1, out second1, out millisecond1);
        XDate.XLDateToCalendarDate (_max, out year2, out month2, out day2,
            out hour2, out minute2, out second2, out millisecond2);

        switch (_majorUnit)
        {
            case DateUnit.Year:
            default:
                nTics = (int)((year2 - year1) / _majorStep + 1.001);
                break;
            case DateUnit.Month:
                nTics = (int)((month2 - month1 + 12.0 * (year2 - year1)) / _majorStep + 1.001);
                break;
            case DateUnit.Day:
                nTics = (int)((_max - _min) / _majorStep + 1.001);
                break;
            case DateUnit.Hour:
                nTics = (int)((_max - _min) / (_majorStep / XDate.HoursPerDay) + 1.001);
                break;
            case DateUnit.Minute:
                nTics = (int)((_max - _min) / (_majorStep / XDate.MinutesPerDay) + 1.001);
                break;
            case DateUnit.Second:
                nTics = (int)((_max - _min) / (_majorStep / XDate.SecondsPerDay) + 1.001);
                break;
            case DateUnit.Millisecond:
                nTics = (int)((_max - _min) / (_majorStep / XDate.MillisecondsPerDay) + 1.001);
                break;
        }

        if (nTics < 1)
        {
            nTics = 1;
        }
        else if (nTics > 1000)
        {
            nTics = 1000;
        }

        return nTics;
    }

    /// <inheritdoc cref="Scale.PickScale"/>
    /// <seealso cref="Scale.PickScale"/>
    /// <seealso cref="AxisType.Date"/>
    /// <seealso cref="Scale.MajorUnit"/>
    /// <seealso cref="Scale.MinorUnit"/>
    public override void PickScale
        (
            GraphPane pane,
            Graphics graphics,
            float scaleFactor
        )
    {
        // call the base class first
        base.PickScale (pane, graphics, scaleFactor);

        // Test for trivial condition of range = 0 and pick a suitable default
        if (_max - _min < 1.0e-20)
        {
            if (_maxAuto)
            {
                _max = _max + 0.2 * (_max == 0 ? 1.0 : Math.Abs (_max));
            }

            if (_minAuto)
            {
                _min = _min - 0.2 * (_min == 0 ? 1.0 : Math.Abs (_min));
            }
        }

        var targetSteps = (_ownerAxis is XAxis || _ownerAxis is X2Axis)
            ? Default.TargetXSteps
            : Default.TargetYSteps;

        // Calculate the step size based on target steps
        var tempStep = CalcDateStepSize (_max - _min, targetSteps);

        // Calculate the new step size
        if (_majorStepAuto)
        {
            _majorStep = tempStep;

            if (_isPreventLabelOverlap)
            {
                // Calculate the maximum number of labels
                var maxLabels = (double)CalcMaxLabels (graphics, pane, scaleFactor);

                if (maxLabels < CalcNumTics())
                {
                    _majorStep = CalcDateStepSize (_max - _min, maxLabels);
                }
            }
        }

        // Calculate the scale minimum
        if (_minAuto)
        {
            _min = CalcEvenStepDate (_min, -1);
        }

        // Calculate the scale maximum
        if (_maxAuto)
        {
            _max = CalcEvenStepDate (_max, 1);
        }

        _mag = 0; // Never use a magnitude shift for date scales

        //this.numDec = 0;		// The number of decimal places to display is not used
    }

    /// <summary>
    /// Calculate a step size for a <see cref="AxisType.Date"/> scale.
    /// This method is used by <see cref="PickScale"/>.
    /// </summary>
    /// <param name="range">The range of data in units of days</param>
    /// <param name="targetSteps">The desired "typical" number of steps
    /// to divide the range into</param>
    /// <returns>The calculated step size for the specified data range.  Also
    /// calculates and sets the values for <see cref="Scale.MajorUnit"/>,
    /// <see cref="Scale.MinorUnit"/>, <see cref="Scale.MinorStep"/>, and
    /// <see cref="Scale.Format"/></returns>
    protected double CalcDateStepSize
        (
            double range,
            double targetSteps
        )
    {
        return CalcDateStepSize (range, targetSteps, this);
    }

    /// <summary>
    /// Calculate a step size for a <see cref="AxisType.Date"/> scale.
    /// This method is used by <see cref="PickScale"/>.
    /// </summary>
    /// <param name="range">The range of data in units of days</param>
    /// <param name="targetSteps">The desired "typical" number of steps
    /// to divide the range into</param>
    /// <param name="scale">
    /// The <see cref="Scale" /> object on which to calculate the Date step size.</param>
    /// <returns>The calculated step size for the specified data range.  Also
    /// calculates and sets the values for <see cref="Scale.MajorUnit"/>,
    /// <see cref="Scale.MinorUnit"/>, <see cref="Scale.MinorStep"/>, and
    /// <see cref="Scale.Format"/></returns>
    internal static double CalcDateStepSize
        (
            double range,
            double targetSteps,
            Scale scale
        )
    {
        // Calculate an initial guess at step size
        var tempStep = range / targetSteps;

        if (range > Default.RangeYearYear)
        {
            scale._majorUnit = DateUnit.Year;
            if (scale._formatAuto)
            {
                scale._format = Default.FormatYearYear;
            }

            tempStep = Math.Ceiling (tempStep / 365.0);

            if (scale._minorStepAuto)
            {
                scale._minorUnit = DateUnit.Year;
                if (tempStep == 1.0)
                {
                    scale._minorStep = 0.25;
                }
                else
                {
                    scale._minorStep = CalcStepSize (tempStep, targetSteps);
                }
            }
        }
        else if (range > Default.RangeYearMonth)
        {
            scale._majorUnit = DateUnit.Year;
            if (scale._formatAuto)
            {
                scale._format = Default.FormatYearMonth;
            }

            tempStep = Math.Ceiling (tempStep / 365.0);

            if (scale._minorStepAuto)
            {
                scale._minorUnit = DateUnit.Month;

                // Calculate the minor steps to give an estimated 4 steps
                // per major step.
                scale._minorStep = Math.Ceiling (range / (targetSteps * 3) / 30.0);

                // make sure the minorStep is 1, 2, 3, 6, or 12 months
                if (scale._minorStep > 6)
                {
                    scale._minorStep = 12;
                }
                else if (scale._minorStep > 3)
                {
                    scale._minorStep = 6;
                }
            }
        }
        else if (range > Default.RangeMonthMonth)
        {
            scale._majorUnit = DateUnit.Month;
            if (scale._formatAuto)
            {
                scale._format = Default.FormatMonthMonth;
            }

            tempStep = Math.Ceiling (tempStep / 30.0);

            if (scale._minorStepAuto)
            {
                scale._minorUnit = DateUnit.Month;
                scale._minorStep = tempStep * 0.25;
            }
        }
        else if (range > Default.RangeDayDay)
        {
            scale._majorUnit = DateUnit.Day;
            if (scale._formatAuto)
            {
                scale._format = Default.FormatDayDay;
            }

            tempStep = Math.Ceiling (tempStep);

            if (scale._minorStepAuto)
            {
                scale._minorUnit = DateUnit.Day;
                scale._minorStep = tempStep * 0.25;

                // make sure the minorStep is 1, 2, 3, 6, or 12 hours
            }
        }
        else if (range > Default.RangeDayHour)
        {
            scale._majorUnit = DateUnit.Day;
            if (scale._formatAuto)
            {
                scale._format = Default.FormatDayHour;
            }

            tempStep = Math.Ceiling (tempStep);

            if (scale._minorStepAuto)
            {
                scale._minorUnit = DateUnit.Hour;

                // Calculate the minor steps to give an estimated 4 steps
                // per major step.
                scale._minorStep = Math.Ceiling (range / (targetSteps * 3) * XDate.HoursPerDay);

                // make sure the minorStep is 1, 2, 3, 6, or 12 hours
                if (scale._minorStep > 6)
                {
                    scale._minorStep = 12;
                }
                else if (scale._minorStep > 3)
                {
                    scale._minorStep = 6;
                }
                else
                {
                    scale._minorStep = 1;
                }
            }
        }
        else if (range > Default.RangeHourHour)
        {
            scale._majorUnit = DateUnit.Hour;
            tempStep = Math.Ceiling (tempStep * XDate.HoursPerDay);
            if (scale._formatAuto)
            {
                scale._format = Default.FormatHourHour;
            }

            if (tempStep > 12.0)
            {
                tempStep = 24.0;
            }
            else if (tempStep > 6.0)
            {
                tempStep = 12.0;
            }
            else if (tempStep > 2.0)
            {
                tempStep = 6.0;
            }
            else if (tempStep > 1.0)
            {
                tempStep = 2.0;
            }
            else
            {
                tempStep = 1.0;
            }

            if (scale._minorStepAuto)
            {
                scale._minorUnit = DateUnit.Hour;
                if (tempStep <= 1.0)
                {
                    scale._minorStep = 0.25;
                }
                else if (tempStep <= 6.0)
                {
                    scale._minorStep = 1.0;
                }
                else if (tempStep <= 12.0)
                {
                    scale._minorStep = 2.0;
                }
                else
                {
                    scale._minorStep = 4.0;
                }
            }
        }
        else if (range > Default.RangeHourMinute)
        {
            scale._majorUnit = DateUnit.Hour;
            tempStep = Math.Ceiling (tempStep * XDate.HoursPerDay);

            if (scale._formatAuto)
            {
                scale._format = Default.FormatHourMinute;
            }

            if (scale._minorStepAuto)
            {
                scale._minorUnit = DateUnit.Minute;

                // Calculate the minor steps to give an estimated 4 steps
                // per major step.
                scale._minorStep = Math.Ceiling (range / (targetSteps * 3) * XDate.MinutesPerDay);

                // make sure the minorStep is 1, 5, 15, or 30 minutes
                if (scale._minorStep > 15.0)
                {
                    scale._minorStep = 30.0;
                }
                else if (scale._minorStep > 5.0)
                {
                    scale._minorStep = 15.0;
                }
                else if (scale._minorStep > 1.0)
                {
                    scale._minorStep = 5.0;
                }
                else
                {
                    scale._minorStep = 1.0;
                }
            }
        }
        else if (range > Default.RangeMinuteMinute)
        {
            scale._majorUnit = DateUnit.Minute;
            if (scale._formatAuto)
            {
                scale._format = Default.FormatMinuteMinute;
            }

            tempStep = Math.Ceiling (tempStep * XDate.MinutesPerDay);

            // make sure the minute step size is 1, 5, 15, or 30 minutes
            if (tempStep > 15.0)
            {
                tempStep = 30.0;
            }
            else if (tempStep > 5.0)
            {
                tempStep = 15.0;
            }
            else if (tempStep > 1.0)
            {
                tempStep = 5.0;
            }
            else
            {
                tempStep = 1.0;
            }

            if (scale._minorStepAuto)
            {
                scale._minorUnit = DateUnit.Minute;
                if (tempStep <= 1.0)
                {
                    scale._minorStep = 0.25;
                }
                else if (tempStep <= 5.0)
                {
                    scale._minorStep = 1.0;
                }
                else
                {
                    scale._minorStep = 5.0;
                }
            }
        }
        else if (range > Default.RangeMinuteSecond)
        {
            scale._majorUnit = DateUnit.Minute;
            tempStep = Math.Ceiling (tempStep * XDate.MinutesPerDay);

            if (scale._formatAuto)
            {
                scale._format = Default.FormatMinuteSecond;
            }

            if (scale._minorStepAuto)
            {
                scale._minorUnit = DateUnit.Second;

                // Calculate the minor steps to give an estimated 4 steps
                // per major step.
                scale._minorStep = Math.Ceiling (range / (targetSteps * 3) * XDate.SecondsPerDay);

                // make sure the minorStep is 1, 5, 15, or 30 seconds
                if (scale._minorStep > 15.0)
                {
                    scale._minorStep = 30.0;
                }
                else if (scale._minorStep > 5.0)
                {
                    scale._minorStep = 15.0;
                }
                else if (scale._minorStep > 1.0)
                {
                    scale._minorStep = 5.0;
                }
                else
                {
                    scale._minorStep = 1.0;
                }
            }
        }
        else if (range > Default.RangeSecondSecond) // SecondSecond
        {
            scale._majorUnit = DateUnit.Second;
            if (scale._formatAuto)
            {
                scale._format = Default.FormatSecondSecond;
            }

            tempStep = Math.Ceiling (tempStep * XDate.SecondsPerDay);

            // make sure the second step size is 1, 5, 15, or 30 seconds
            if (tempStep > 15.0)
            {
                tempStep = 30.0;
            }
            else if (tempStep > 5.0)
            {
                tempStep = 15.0;
            }
            else if (tempStep > 1.0)
            {
                tempStep = 5.0;
            }
            else
            {
                tempStep = 1.0;
            }

            if (scale._minorStepAuto)
            {
                scale._minorUnit = DateUnit.Second;
                if (tempStep <= 1.0)
                {
                    scale._minorStep = 0.25;
                }
                else if (tempStep <= 5.0)
                {
                    scale._minorStep = 1.0;
                }
                else
                {
                    scale._minorStep = 5.0;
                }
            }
        }
        else // MilliSecond
        {
            scale._majorUnit = DateUnit.Millisecond;
            if (scale._formatAuto)
            {
                scale._format = Default.FormatMillisecond;
            }

            tempStep = CalcStepSize (range * XDate.MillisecondsPerDay, Default.TargetXSteps);

            if (scale._minorStepAuto)
            {
                scale._minorStep = CalcStepSize (tempStep,
                    (scale._ownerAxis is XAxis || scale._ownerAxis is X2Axis)
                        ? Default.TargetMinorXSteps
                        : Default.TargetMinorYSteps);
                scale._minorUnit = DateUnit.Millisecond;
            }
        }

        return tempStep;
    }

    /// <summary>
    /// Calculate a date that is close to the specified date and an
    /// even multiple of the selected
    /// <see cref="Scale.MajorUnit"/> for a <see cref="AxisType.Date"/> scale.
    /// This method is used by <see cref="PickScale"/>.
    /// </summary>
    /// <param name="date">The date which the calculation should be close to</param>
    /// <param name="direction">The desired direction for the date to take.
    /// 1 indicates the result date should be greater than the specified
    /// date parameter.  -1 indicates the other direction.</param>
    /// <returns>The calculated date</returns>
    protected double CalcEvenStepDate (double date, int direction)
    {
        int year, month, day, hour, minute, second, millisecond;

        XDate.XLDateToCalendarDate (date, out year, out month, out day,
            out hour, out minute, out second, out millisecond);

        // If the direction is -1, then it is sufficient to go to the beginning of
        // the current time period, .e.g., for 15-May-95, and monthly steps, we
        // can just back up to 1-May-95
        if (direction < 0)
        {
            direction = 0;
        }

        switch (_majorUnit)
        {
            case DateUnit.Year:
            default:
                // If the date is already an exact year, then don't step to the next year
                if (direction == 1 && month == 1 && day == 1 && hour == 0
                    && minute == 0 && second == 0)
                {
                    return date;
                }
                else
                {
                    return XDate.CalendarDateToXLDate (year + direction, 1, 1,
                        0, 0, 0);
                }
            case DateUnit.Month:
                // If the date is already an exact month, then don't step to the next month
                if (direction == 1 && day == 1 && hour == 0
                    && minute == 0 && second == 0)
                {
                    return date;
                }
                else
                {
                    return XDate.CalendarDateToXLDate (year, month + direction, 1,
                        0, 0, 0);
                }
            case DateUnit.Day:
                // If the date is already an exact Day, then don't step to the next day
                if (direction == 1 && hour == 0 && minute == 0 && second == 0)
                {
                    return date;
                }
                else
                {
                    return XDate.CalendarDateToXLDate (year, month,
                        day + direction, 0, 0, 0);
                }
            case DateUnit.Hour:
                // If the date is already an exact hour, then don't step to the next hour
                if (direction == 1 && minute == 0 && second == 0)
                {
                    return date;
                }
                else
                {
                    return XDate.CalendarDateToXLDate (year, month, day,
                        hour + direction, 0, 0);
                }
            case DateUnit.Minute:
                // If the date is already an exact minute, then don't step to the next minute
                if (direction == 1 && second == 0)
                {
                    return date;
                }
                else
                {
                    return XDate.CalendarDateToXLDate (year, month, day, hour,
                        minute + direction, 0);
                }
            case DateUnit.Second:
                return XDate.CalendarDateToXLDate (year, month, day, hour,
                    minute, second + direction);

            case DateUnit.Millisecond:
                return XDate.CalendarDateToXLDate (year, month, day, hour,
                    minute, second, millisecond + direction);
        }
    }

    /// <inheritdoc cref="Scale.MakeLabel"/>
    internal override string MakeLabel (GraphPane pane, int index, double dVal)
    {
        if (_format == null)
        {
            _format = Default.Format;
        }

        return XDate.ToString (dVal, _format);
    }

    /// <inheritdoc cref="Scale.MajorUnitMultiplier"/>
    internal override double MajorUnitMultiplier => GetUnitMultiple (_majorUnit);

    /// <inheritdoc cref="Scale.MinorUnitMultiplier"/>
    internal override double MinorUnitMultiplier => GetUnitMultiple (_minorUnit);

    /// <summary>
    /// Internal routine to calculate a multiplier to the selected unit back to days.
    /// </summary>
    /// <param name="unit">The unit type for which the multiplier is to be
    /// calculated</param>
    /// <returns>
    /// This is ratio of days/selected unit
    /// </returns>
    private double GetUnitMultiple (DateUnit unit)
    {
        return unit switch
        {
            DateUnit.Year => 365.0,
            DateUnit.Month => 30.0,
            DateUnit.Day => 1.0,
            DateUnit.Hour => 1.0 / XDate.HoursPerDay,
            DateUnit.Minute => 1.0 / XDate.MinutesPerDay,
            DateUnit.Second => 1.0 / XDate.SecondsPerDay,
            DateUnit.Millisecond => 1.0 / XDate.MillisecondsPerDay,
            _ => 365.0
        };
    }

    #endregion

    #region Serialization

    /// <summary>
    /// Current schema value that defines the version of the serialized file
    /// </summary>
    public const int schema2 = 10;

    /// <summary>
    /// Constructor for deserializing objects
    /// </summary>
    /// <param name="info">A <see cref="SerializationInfo"/> instance that defines the serialized data
    /// </param>
    /// <param name="context">A <see cref="StreamingContext"/> instance that contains the serialized data
    /// </param>
    protected DateScale
        (
            SerializationInfo info,
            StreamingContext context
        )
        : base (info, context)
    {
        // The schema value is just a file version parameter.  You can use it to make future versions
        // backwards compatible as new member variables are added to classes
        info.GetInt32 ("schema2").NotUsed();
    }

    /// <inheritdoc cref="ISerializable.GetObjectData"/>
    public override void GetObjectData
        (
            SerializationInfo info,
            StreamingContext context
        )
    {
        base.GetObjectData (info, context);
        info.AddValue ("schema2", schema2);
    }

    #endregion
}
